/************************************************************************
Copyright 2003, 2004, 2005, 2006, 2007, 2008 Grid Technology Research Center,
National Institute of Advanced Industrial Science and Technology.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
************************************************************************/

package jp.aist.gtrc.plus.scheduler.specific.sge.sched;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import jp.aist.gtrc.plus.scheduler.specific.sge.jaxb.common.JAXBSGEServer;
import jp.aist.gtrc.plus.scheduler.sys.SchedulerException;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

class OpedXMLNormalizer {

	private static DocumentBuilder builder;
	private static Unmarshaller unmarshaller;

	private static final String packageName = JAXBSGEServer.class.getPackage().getName();
	protected static final Logger logger = Logger.getLogger(OpedXMLNormalizer.class);

	private static synchronized void init() throws SchedulerException {

		if (builder == null) {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			factory.setValidating(true);
			factory.setAttribute(
				"http://java.sun.com/xml/jaxp/properties/schemaLanguage",
				"http://www.w3.org/2001/XMLSchema");

			try {
				builder = factory.newDocumentBuilder();
				builder.setErrorHandler(new DefaultHandler());
			} catch (ParserConfigurationException e) {
				throw new SchedulerException("Cannot initialize DOM parser");
			}
		}
		if (unmarshaller == null) {
			try {
				JAXBContext context = JAXBContext.newInstance(packageName);
				unmarshaller = context.createUnmarshaller();
			} catch (Exception e) {
				throw new SchedulerException("Cannot initalize JAXB");
			}
		}

	}

	public OpedXMLNormalizer() throws SchedulerException {

		init();

	}

	private String normalizeXML(String objName, String xml) {

		/*
		 * Replace some illegal (contains space) tags before XML parsing.
		 */
		final String[][] checkTable = {
				{ "job",	"context list", "context_list" },
				{ "pe",		"parallel environment", "parallel_environment" },
				{ "hgroup",	"host group", "host_group" },
				{ "queue",	"cluster queue", "cluster_queue" }
		};
		for (String[] checker : checkTable) {
			if (objName.equals(checker[0]) && xml.contains(checker[1])) {
				xml = xml.replaceAll(checker[1], checker[2]);
			}
		}

		/*
		 * NOTE: SGE 6.0u? or SGE 6.1u2 (builded from source)
		 * has illegal <QINSTANCE:all.q/>...</QINSTANCE:all.q/> tag.
		 * SGE 6.1u2 prebuild-binary has <QINSTANCE>...<QINSTANCE/>.
		 * We replace these tags to <QU_element>...</QU_element> here.
		 */
		if (objName.equals("queue")) {
			if (xml.indexOf("QINSTANCE:") != -1) {
				return xml.replaceAll("QINSTANCE:\\S+/>", "QU_element>");
			}
		}

		return xml;

	}

	private String checkPrefix(Node node) {

		NodeList nodeList = node.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node subNode = nodeList.item(i);
			if (subNode.getNodeType() == Node.ELEMENT_NODE) {
				String name = subNode.getNodeName();
				int index = name.indexOf('_');
				return (index > 0) ? name.substring(0, index +1) : "";
			}
		}
		return "";
	}

	private static final int FIRST_CHILDLEN_DEPTH = 0;

	private void checkNode(Document doc, final int depth, Node node, String objName) {

		NodeList nodeList = node.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node subNode = nodeList.item(i);
			if (subNode.getNodeType() == Node.ELEMENT_NODE) {
				String name = subNode.getNodeName();
				if ((depth == FIRST_CHILDLEN_DEPTH)
						&& (name.equals(objName) == false)) {
					doc.renameNode(subNode, "", objName);
					subNode = nodeList.item(i);
				} else if (name.equals("element")) {
					String newPrefix = checkPrefix(subNode);
					doc.renameNode(subNode, "", newPrefix + name);
					subNode = nodeList.item(i);
				}
				checkNode(doc, depth + 1, subNode, objName);
			}
		}

	}

	private void checkNode(Document doc, Node node, String objName) {
		checkNode(doc, FIRST_CHILDLEN_DEPTH, node, objName);
	}

	private static String lastXMLLine(String objName) {

		return "</" + objName + "_list>";

	}

	public Document makeXMLDocument(InputStream stream, String objName) throws SchedulerException {

		try {
			BufferedReader in = new BufferedReader(
					new InputStreamReader(stream));
			String line;
			String last = lastXMLLine(objName);
			StringBuffer bf = new StringBuffer();
			while ((line = in.readLine()) != null) {
				bf.append(line);
				if (line.startsWith(last))
					break;
			}

			String xml = normalizeXML(objName, bf.toString());
			InputSource is = new InputSource(new StringReader(xml));
			Document document = builder.parse(is);

			NodeList roots = document.getChildNodes();
			if (roots.getLength() != 1) {
				throw new SchedulerException("unexpected XML data structure");
			}

			Node objsNode = roots.item(0);
			checkNode(document, objsNode, objName);
			/*
			 * NOTE: We assume XML such as below style now:
			 *  <objName_list>
			 *    <objName>
			 *      <elemA>dataA</elemA>
			 *      ....
			 *    </objName>
			 *    ...
			 *  </objName_list>
			 */
			return document;
		} catch (IOException e) {
			logger.debug(e);
			throw new SchedulerException("Invalid XML data");
		} catch (SAXException e) {
			logger.debug(e);
			throw new SchedulerException("Invalid XML data");
		}

	}

	public Object makeJAXBObject(InputStream stream, String objName) throws SchedulerException {

		Document doc = makeXMLDocument(stream, objName);
		JAXBElement<?> elem;
		try {
			elem = (JAXBElement<?>)unmarshaller.unmarshal(doc);
		} catch (JAXBException e) {
			logger.debug(e);
			throw new SchedulerException("Cannot parse by JAXB");
		}

		Object obj = (elem != null) ? elem.getValue() : null;
		if (obj != null) {
			return obj;
		} else {
			throw new SchedulerException("Cannot handle by JAXB or unexpected data");
		}

	}

}
